;+
; NAME:
;       MrWidgetBase
;
;*****************************************************************************************
;   Copyright (c) 2013, Matthew Argall                                                   ;
;   All rights reserved.                                                                 ;
;                                                                                        ;
;   Redistribution and use in source and binary forms, with or without modification,     ;
;   are permitted provided that the following conditions are met:                        ;
;                                                                                        ;
;       * Redistributions of source code must retain the above copyright notice,         ;
;         this list of conditions and the following disclaimer.                          ;
;       * Redistributions in binary form must reproduce the above copyright notice,      ;
;         this list of conditions and the following disclaimer in the documentation      ;
;         and/or other materials provided with the distribution.                         ;
;       * Neither the name of the <ORGANIZATION> nor the names of its contributors may   ;
;         be used to endorse or promote products derived from this software without      ;
;         specific prior written permission.                                             ;
;                                                                                        ;
;   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY  ;
;   EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ;
;   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT  ;
;   SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,       ;
;   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED ;
;   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR   ;
;   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN     ;
;   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN   ;
;   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH  ;
;   DAMAGE.                                                                              ;
;*****************************************************************************************
;
; PURPOSE
;+
;   The purpose of this program is to create a widget base class. For top-level bases,
;   use the MrWidgetBase widget class.
;
;   The following keywords to WIDGET_BASE are allowed only for Top Level Bases::
;      MBAR
;      BITMAP
;      FLOATING
;      GROUP_LEADER
;      MODAL
;      TLB_FRAME_ATTR
;      TLB_MOVE_EVENTS
;      TLB_SIZE_EVENTS
;      TLB_KILL_REQUEST_EVENTS
;      TLB_ICONIFY_EVENTS
;       
;
; :Author:
;   Matthew Argall::
;       University of New Hampshire
;       Morse Hall, Room 113
;       8 College Rd.
;       Durham, NH, 03824
;       matthew.argall@wildcats.unh.edu
;
; :History:
;	Modification History::
;       10/15/2013  -   Written by Matthew Argall
;-
;******************************************************************************************
;+
;   General event handler for the MrWidgetBase widget class. Its purpose is to forward
;   the different events generated by the XManager to their respective event handling
;   methods.
;
; :Params:
;       EVENT:              in, optional, type=structure
;                           An event structure returned by the windows manager.
;-
pro MrWidgetBase_Event_Pro, event, $
STATUS=status
    compile_opt strictarr
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return
    endif
    
    ;First, call MrWidgetAtom in case EVENT_OBJ, EVENT_PRO, or EVENT_FUNC are in use.
    if n_elements(status) eq 0 then MrWidgetAtom_Event_Pro, event, STATUS=status
    if status ne 0 then return
    
    ;Type of event that was generated.
    event_name = size(event, /SNAME)
    widget_control, event.id, GET_UVALUE=oRef
    
    ;Forward events to an event handler classes?
    oRef -> GetProperty, EVENT_OBJ=event_obj
    tf_event_obj = obj_valid(event_obj)
    
;---------------------------------------------------------------------
;Callback Pro/Method /////////////////////////////////////////////////
;---------------------------------------------------------------------
    ;Forward the event to the event-handling method
    case event_name of
        'WIDGET_KBRD_FOCUS': begin
            oRef -> GetProperty, KBRD_FOCUS_HANDLER=kbrd_focus_eh
            if kbrd_focus_eh ne '' then begin
                if tf_event_obj $
                    then Call_Method, kbrd_focus_eh, event_obj, event $
                    else Call_Procedure, kbrd_focus_eh, event
            endif
        endcase
        
        'WIDGET_CONTEXT': begin
            oRef -> GetProperty, CONTEXT_HANDLER=context_eh
            if context_eh ne '' then begin
                if tf_event_obj $
                    then Call_Method, context_eh, event_obj, event $
                    else Call_Procedure, context_eh, event
            endif
        endcase
        
        else: ;Do nothing
    endcase
end


;+
;   Event handling function for Event_Func.
;
; :Params:
;       EVENT:              in, optional, type=structure
;                           An event structure returned by the windows manager.
;-
function MrWidgetBase_Event_Func, event, $
STATUS=status
    compile_opt strictarr
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, 0
    endif
    
    if n_elements(status) eq 0 $
        then result = MrWidgetAtom_Event_Func(event, STATUS=status)
    case status of
        -1: return, 0
         1: return, result
         else: ;Continue
    endcase
    
    ;Type of event that was generated.
    event_name = size(event, /SNAME)
    widget_control, event.id, GET_UVALUE=oRef
    
    ;Forward events to an event handler classes?
    oRef -> GetProperty, EVENT_OBJ=event_obj
    tf_event_obj = obj_valid(event_obj)
    
;---------------------------------------------------------------------
;Callback Func/Method ////////////////////////////////////////////////
;---------------------------------------------------------------------
    ;Forward the event to the event-handling method
    case event_name of
        'WIDGET_KBRD_FOCUS': begin
            oRef -> GetProperty, KBRD_FOCUS_HANDLER=kbrd_focus_eh
            if kbrd_focus_eh ne '' then begin
                if tf_event_obj $
                    then result = Call_Method(kbrd_focus_eh, event_obj, event) $
                    else result = Call_Function(kbrd_focus_eh, event)
            endif
        endcase
        
        'WIDGET_CONTEXT': begin
            oRef -> GetProperty, CONTEXT_HANDLER=context_eh
            if context_eh ne '' then begin
                if tf_event_obj $
                    then result = Call_Method(context_eh, event_obj, event) $
                    else result = Call_Function(context_eh, event)
            endif
        endcase
        
        else: ;Do nothing
    endcase
    
    if n_elements(result) eq 0 then result = 0
    return, result
end


;+
;   This method processes context events.
;
; :Params:
;       EVENT:              in, optional, type=structure
;                           An event structure returned by the windows manager.
;-
pro MrWidgetBase::Context_Events, event
    ;Nothing to do yet.
end



;+
;   This method is used to realize and draw the widget hierarchy. It also starts
;   XMANAGER to managing the widget hierarchy.
;
; KEYWORDS:
;
;       BLOCK:         Set this keyword to create a blocking widget hierarchy.
;
;       CENTER:        Set this keyword to center the TLB before display.
;
;       REGISTER_NAME: The name by which the program will be registered with XManager.
;
;       GROUP_LEADER:  The widget identifier of a group leader for this widget hierarchy.
;
;       JUST_REGISTER: Set his keyword to just register with XManager, but not to fire it up.
;
;       _EXTRA:        To pass additional keywords BaseWidget::Draw
;-
PRO MrWidgetBase::Draw, $
CENTER=center, $
EVENT_HANDLER = event_handler, $
REGISTER_NAME=register_name, $
BLOCK=block, $
GROUP_LEADER=group_leader, $
JUST_REGISTER=just_register, $
XOFFSET=xoffset, $
YOFFSET=yoffset
    compile_opt strictarr
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return
    endif

    ;Event Handler
    if n_elements(event_handler) eq 0 then begin
         if self._event_pro ne '' $
            then event_handler = self._event_pro $
            else event_handler = 'MrWidgetBase_Event_Pro'
    endif

    ;Position the base on the screen
    if keyword_set(center) $
        then self -> Position $
        else if (n_elements(xoffset) ne 0) or (n_elements(yoffset) ne 0) $
            then self -> Position, xoffset, yoffset

    ;Get a unique name if one is not provided.
    if self._register_name eq "" then begin
        if (n_elements(register_name) eq 0) $
            then self._register_name = 'Program_' + strtrim(self._id, 2) $
            else self._register_name = register_name
    endif

    ;Realize the widget hierarchy.
    if widget_info(self._id, /REALIZED) eq 0 then self -> realize

    ;Start xmanager
    if keyword_set(just_register) then begin
        xmanager, self._register_name, self._id, JUST_REG=1, /NO_BLOCK, $
                  EVENT_HANDLER=event_handler, CLEANUP=cleanup
    endif else begin
        xmanager, self._register_name, self._id, GROUP_LEADER=group_leader, $
                  NO_BLOCK=1-keyword_set(block), EVENT_HANDLER=event_handler
    endelse
end


;+
;   This method is used to obtain the MrWidgetBase object's properties
;
; KEYWORDS:
;
;       ICONIFY_EVENTS: Set to 1 if ICONIFY events are set for this top-level base widget.
;
;       MAP:            Returns a 0 or 1 to indicate if the current base widget hierarchy is
;                       mapped (1) or not (0).
;
;       MODAL:          Returns a 0 or 1 to indicate if the current base widget hierarchy is modal (1) or not (0).
;
;       MOVE_EVENTS:    Set to 1 if move events are set for this top-level base widget.
;
;       SIZE_EVENTS:    Set to 1 if size events are set for this top-level base widget.
;
;       _EXTRA:         Any keyword appropriate for the supercalss Draw methods.
;-
PRO MrWidgetBase::GetProperty, $
 ;Events On or Off?
 CONTEXT_EVENTS=context_events, $
 KBRD_FOCUS_EVENTS=kbrd_focus_events, $
 ;Callback Func/Pro/Method/Object
 KBRD_FOCUS_HANDLER=kbrd_focus_handler, $
 CONTEXT_HANDLER=context_hanlder, $
_REF_EXTRA=extra
    compile_opt strictarr
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return
    endif
    
;---------------------------------------------------------------------
;Widget_Info & Widget_Control Options ////////////////////////////////
;---------------------------------------------------------------------
    ;Superclass
    if n_elements(extra) gt 0 then self -> MrWidgetAtom::GetProperty, _STRICT_EXTRA=extra
    
;---------------------------------------------------------------------
;Are Events On or Off? ///////////////////////////////////////////////
;---------------------------------------------------------------------
    if arg_present(context_events)    then context_events    = widget_info(self._id, /CONTEXT_EVENTS)
    if arg_present(kbrd_focus_events) then kbrd_focus_events = widget_info(self._id, /KBRD_FOCUS_EVENTS)
    
;---------------------------------------------------------------------
;Callback Func/Pro/Method/Object /////////////////////////////////////
;---------------------------------------------------------------------
    if arg_present(context_handler)    then context_handler    = self._context_handler
    if arg_present(kbrd_focus_handler) then kbrd_focus_handler = self._kbrd_focus_handler
end


;+
;   This method processes keyword focus events.
;
; :Params:
;       EVENT:              in, optional, type=structure
;                           An event structure returned by the windows manager.
;-
pro MrWidgetBase::Kbrd_Focus_Events, event
    ;Nothing to do yet.
end


;+
;   This method is used to set the MrWidgetBase object's properties
;-
pro MrWidgetBase::SetProperty, $
ICONIFY=iconify, $
TAB_MODE=tab_mode, $
TITLE=title, $

;Turn Events On or Off
CONTEXT_EVENTS=context_events, $
KBRD_FOCUS_EVENTS=kbrd_focus_events, $
 
;Callback Functions/Methods/Procedures
KBRD_FOCUS_HANDLER=kbrd_focus_handler, $
CONTEXT_HANDLER=context_hanlder, $
_REF_EXTRA=extra
    compile_opt strictarr
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return
    endif

    
;---------------------------------------------------------------------
;Widget_Control Options //////////////////////////////////////////////
;---------------------------------------------------------------------
    if n_elements(iconify)  gt 0 then widget_control, self._id, ICONIFY=keyword_set(iconify)
    if n_elements(tab_mode) gt 0 then widget_control, self._id, TAB_MODE=tab_mode
    if n_elements(title)    gt 0 then widget_control, self._id, BASE_SET_TITLE=title

    ;Superclass
    if n_elements(extra) gt 0 then self -> MrWidgetAtom::SetProperty, _STRICT_EXTRA=extra
    
;---------------------------------------------------------------------
;Turn Events On or Off ///////////////////////////////////////////////
;---------------------------------------------------------------------
    if n_elements(context_events)    ne 0 then widget_control, self._id, CONTEXT_EVENTS=keyword_set(context_events)
    if n_elements(kbrd_focus_events) ne 0 then widget_control, self._id, KBRD_FOCUS_EVENTS=keyword_set(kbrd_focus_events)

;---------------------------------------------------------------------
;Callback Functions/Methods/Procedures ///////////////////////////////
;---------------------------------------------------------------------
    
    if n_elements(context_handler)    gt 0 then self._context_handler    = context_handler            
    if n_elements(kbrd_focus_handler) gt 0 then self._kbrd_focus_handler = kbrd_focus_handler            
end


;+
;   This is the MrWidgetBase object class destructor method.
;-
pro MrWidgetBase::cleanup
    compile_opt strictarr
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return
    endif
    
    ;Clean up the superclasses
    self -> MrWidgetAtom::Cleanup
end


;+
;   This is the MrWidgetBase object class initialization method
;
; :Keywords:
;       EVENT_FUNC:             in, optional, type=string, default=''
;                               A string specifying the name of a function to be called
;                                   when events are generated. This procedure will handle
;                                   `TLB_SIZE_EVENTS`, `TLB_MOVE_EVENTS`, `TLB_IDONIFY_EVENTS`,
;                                   `KBRD_FOCUS_EVENTS`, `TLB_KILL_REQUEST_EVENTS`, and
;                                   `CONTEXT_EVENTS`. If neither `EVENT_FUNC` nor `EVENT_PRO`
;                                   are set and these keywords are structures, the default
;                                   is to use 'MrWidgetBase_Event_Pro' to forward events
;                                   (see `EVENT_PRO`). If the methods given are functions,
;                                   set this keyword to 'MrWidgetBase_Event_Func' instead.
;       EVENT_PRO:              in, optional, type=string, default=''
;                               A string specifying the name of a procedure to be called
;                                   when events are generated. This procedure will handle
;                                   `TLB_SIZE_EVENTS`, `TLB_MOVE_EVENTS`, `TLB_IDONIFY_EVENTS`,
;                                   `KBRD_FOCUS_EVENTS`, `TLB_KILL_REQUEST_EVENTS`, and
;                                   `CONTEXT_EVENTS`. These keywords can be structures
;                                   (instead of boolean values) of the form::
;                                       KWRD = {object: objRef, $
;                                               method: 'Event_Handler'}
;                                   where "objRef" is a valid object reference and
;                                   "Event_Handler" is a string containing the name of a
;                                   method to be used to handle events. In this case, the
;                                   procedure 'MrWidgetBase_Event_Pro' will forward events
;                                   to the proper event handling method. Finally, note that
;                                   if XManager is handling events, top level bases should
;                                   use the EVENT_HANDLER keyword to XManager instead of
;                                   EVENT_PRO. If XManager is called (in the Draw method)
;                                   and EVENT_HANDLER is not specified, then EVENT_PRO will
;                                   be used (if provided).
;       KBRD_FOCUS_EVENTS:      in, optional, type=boolean/structure, default=0
;                               If set, keyboard focus events will be turned on. See
;                                   `EVENT_PRO` for handling events with object methods.
;       KILL_NOTIFY:            in, optional, type=string/structure, default=''
;                               A string containing the name of a procedure to be called
;                                   when the widget dies. If a structure is provided
;                                   (see `EVENT_PRO`), then 'MrWidgetBase_Kill_Notify'
;                                   will forward events to the given object method.
;       NOTIFY_REALIZE:         in, optional, type=string/structure, default=''
;                               A string containing the name of a procedure to be called
;                                   when the widget is realized. If a structure is provided
;                                   (see `EVENT_PRO`), then 'MrWidgetBase_Notify_Realize'
;                                   will forward events to the given object method.
;       _REF_EXTRA:             in, optional, type=any
;                               Any keyword accepted by MrWidgetAtom::Init is allowed.
;-
function MrWidgetBase::init, parent, $
    ALIGN_BOTTOM=align_bottom,$
    ALIGN_CENTER=align_center, $
    ALIGN_LEFT=align_left, $
    ALIGN_RIGHT=align_right, $
    ALIGN_TOP=align_top, $
    BASE_ALIGN_BOTTOM=base_align_bottom, $
    BASE_ALIGN_CENTER=base_align_center, $
    BASE_ALIGN_LEFT=base_align_left, $
    BASE_ALIGN_RIGHT=base_align_right, $
    BASE_ALIGN_TOP=base_align_top, $
    COLUMN=column, $
    CONTEXT_EVENTS=context_events, $
    CONTEXT_MENU=context_menu, $
    EXCLUSIVE=exclusive, $
    FRAME=frame, $
    GRID_LAYOUT=grid_layout, $
    KBRD_FOCUS_EVENTS=kbrd_focus_events, $
    MAP=map, $
    NONEXCLUSIVE=nonexclusive, $
    NOTIFY_REALIZE=notify_realize, $
    ROW=row, $
    SCR_XSIZE=scr_xsize, $
    SCR_YSIZE=scr_ysize, $
    SCROLL=scroll, $
    SPACE=space, $
    TITLE=title, $
    TOOLBAR=toolbar, $
    UNITS=units, $
    X_SCROLL_SIZE=x_scroll_size, $
    XOFFSET=xoffset, $
    XPAD=xpad, $
    XSIZE=xsize, $
    Y_SCROLL_SIZE=y_scroll_size, $
    YOFFSET=yoffset, $
    YPAD=ypad, $
    YSIZE=ysize, $
   _REF_EXTRA=extra
    
    ;Error handling
    catch, the_error
    if the_error ne 0 then begin
        catch, /cancel
        void = cgErrorMsg()
        return, 0
    endif
    
    ;Defaults
    if n_elements(title) eq 0 then title = 'MrBaseWidget'

;---------------------------------------------------------------------
;Create the Widget ///////////////////////////////////////////////////
;---------------------------------------------------------------------
    ;Accept MrWidget items as a parent
    if cgObj_IsA(parent, 'MrWidgetAtom') $
        then parent -> GetProperty, ID=parentID $
        else parentID = parent

    ;Create the base
    self._id = WIDGET_BASE( parentID, $
                            ALIGN_BOTTOM      = align_bottom,$
                            ALIGN_CENTER      = align_center, $
                            ALIGN_LEFT        = align_left, $
                            ALIGN_RIGHT       = align_right, $
                            ALIGN_TOP         = align_top, $
                            BASE_ALIGN_BOTTOM = base_align_bottom, $
                            BASE_ALIGN_CENTER = base_align_center, $
                            BASE_ALIGN_LEFT   = base_align_left, $
                            BASE_ALIGN_RIGHT  = base_align_right, $
                            BASE_ALIGN_TOP    = base_align_top, $
                            COLUMN            = column, $
                            CONTEXT_MENU      = context_menu, $
                            EXCLUSIVE         = exclusive, $
                            FRAME             = frame, $
                            GRID_LAYOUT       = grid_layout, $
                            MAP               = map, $
                            NONEXCLUSIVE      = nonexclusive, $
                            NOTIFY_REALIZE    = notify_realize, $
                            ROW               = row, $
                            SCR_XSIZE         = scr_xsize, $
                            SCR_YSIZE         = scr_ysize, $
                            SCROLL            = scroll, $
                            SPACE             = space, $
                            TITLE             = title, $
                            TOOLBAR           = toolbar, $
                            UNITS             = units, $
                            X_SCROLL_SIZE     = x_scroll_size, $
                            XOFFSET           = xoffset, $
                            XPAD              = xpad, $
                            XSIZE             = xsize, $
                            Y_SCROLL_SIZE     = y_scroll_size, $
                            YOFFSET           = yoffset, $
                            YPAD              = ypad, $
                            YSIZE             = ysize $
                          )
    
;---------------------------------------------------------------------
;Create the Widget ///////////////////////////////////////////////////
;---------------------------------------------------------------------

    ;Superclass
    success = self -> MrWidgetAtom::INIT(NOTIFY_REALIZE=notify_realize, $
                                        _STRICT_EXTRA=extra)
    if success eq 0 then message, 'MrWidgetAtom could not be initialized.'
    
    ;Object Properties
    self -> SetProperty, CONTEXT_HANDLER=context_handler, $
                         KBRD_FOCUS_HANDLER=kbrd_focus_handler

    return, 1
end


;+
;   The class definition statement.
;
; :Fields:
;       _CONTEXT_HANDLER:       Method event handler for context events.
;       _KBRD_FOCUS_HANDLER:    Method event handler for keyboard focus events.
;
; :Params:
;       CLASS:          out, optional, type=structure
;                       The class definition structure.
;-
pro MrWidgetBase__define, class
    compile_opt strictarr

    class =  { MrWidgetBase, $
               inherits MrWidgetAtom, $

               ;Event Handling Methods
               _context_handler:    '', $
               _kbrd_focus_handler: ''  $
            }
end
